import {
  AntDesignTheme,
  cadAppErrorLog,
  CMEVENTS,
  designTreeMenuManager,
  IAssemblyConstraint,
  IAssemblyDocTreeNode,
  Icon,
  PickFrom,
  STATE,
  Types,
  useCommandManager,
  FeatureManager,
} from '@amcax/base';
import { getAllKeysOfRefDoc, PickerAssembly, PickerMgr } from '@amcax/renderer';
import AntIcon from '@ant-design/icons';
import { csEvent } from '@app-base/common/events/EventCenter';
import { CADIcons } from '@app-cad-env/nextcad/widgets/LeftPanel';
import { ConstraintType } from '@app-cad-env/product/commands/cmd-constrain/type';
import RenameDocModal from '@app-cad-env/product/widgets/LeftPanel/modals/RenameDocModal';
import ResortDocModal from '@app-cad-env/product/widgets/LeftPanel/modals/ResortDocModal';
import { PickInputEvents } from '@app-cad/common/components/pick-input/types';
import { COMMON_EVENTS, PRODUCT_EVENT } from '@app-cad/common/Constants';
import {
  DocumentContext,
  useDocumentConsumer,
} from '@app-cad/common/providers/documentProvider';
import { ctx2, referenceManager, userEvents } from '@app-cad/module';
import { getProductPartSubModel } from '@app-cad/pages/product-support/part-funcs';
import { ConfigProvider } from 'antd';
import { AntdTreeNodeAttribute } from 'antd/es/tree/Tree';
import React, { useContext, useEffect, useState } from 'react';
import { ProductNodeMenu } from './ContextMenu';
import { ReactComponent as OriginIcon } from './icons/origin.svg';
import { ReactComponent as PartIcon } from './icons/part.svg';
import { ReactComponent as PlaneIcon } from './icons/plane.svg';
import { ReactComponent as ProductIcon } from './icons/product.svg';
import { ExchangeDocModal, InsertDocModal, NewDocModal } from './modals';
import { NodeTitle } from './NodeTitle';
import { StyledTree } from './styledTree';
import { ProductTreeNode } from './types';

const treeIconMap = {
  [Types.ORIGIN]: OriginIcon,
  [Types.BASIC_PLANE]: PlaneIcon,

  product: ProductIcon,
  part: PartIcon,
};

const assemblyConstraintIconMap = {
  // of cmd-constrain-fix
  [ConstraintType.Fix]: 'constraints/fixed',
  // of cmd-constrain
  [ConstraintType.Distance]: 'constraints/distance',
  [ConstraintType.Coincidence]: 'constraints/face',
  [ConstraintType.Angle]: 'constraints/angle',
  [ConstraintType.Parallel]: 'constraints/parallel',
  [ConstraintType.Perpendicular]: 'constraints/perpendicular',
  [ConstraintType.Tangent]: 'constraints/assembly-tangency',
  [ConstraintType.Coincident]: 'constraints/concentric',
};

export const basicRefIDs = ['ORIGIN', 'XOY', 'YOZ', 'XOZ'];

export const LeftPanel = () => {
  const { docTree } = useContext(DocumentContext);
  const { documentInfo } = useDocumentConsumer();
  const [newDocModalOpen, setNewDocModalOpen] = useState(false);
  const [insertDocModalOpen, setInsertDocModalOpen] = useState(false);
  const [renameDocModalOpen, setRenameDocModalOpen] = useState(false);
  const [resortDocModalOpen, setResortDocModalOpen] = useState(false);
  const [exchangeDocModalOpen, setExchangeDocModalOpen] = useState(false);

  const [nodeToRename, setNodeToRename] = useState<ProductTreeNode | null>(
    null,
  );
  const [nodeToResort, setNodeToResort] = useState<ProductTreeNode | null>(
    null,
  );
  const [expandedKeys, setExpandedKeys] = useState<string[]>([docTree.key]);
  const [nodeToExchange, setNodeToExchange] = useState<ProductTreeNode | null>(
    null,
  );
  const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
  const [selectedNodeChain, setSelectedNodeChain] = useState<
    IAssemblyDocTreeNode[]
  >([]);

  const [pickInputPickedTarget, setPickInputPickedTarget] = useState<
    string | undefined
  >();

  const { currentCmd, invokeCmd } = useCommandManager();

  const treeData = [docTree];
  const isRoot = (node: ProductTreeNode) => node.key === docTree.key;

  const treeIcon = (props: AntdTreeNodeAttribute) => {
    const { type, assembly, originalObj } =
      ((props as any)?.data as ProductTreeNode) || {};
    if (type === 'assembly-constraints-root') {
      return <Icon name='constraints/assembly-constraint' />;
    }
    if (type === 'measurement-root' || type === 'measurement') {
      return <Icon name='commands/feature/measure' />;
    }
    if (type === 'assembly-constraint') {
      const { type } = (originalObj as IAssemblyConstraint) || {};

      return <Icon name={assemblyConstraintIconMap[type]} />;
    }
    if (type === 'array') {
      if ((assembly.params as any).mode === 'linear') {
        return <Icon name='commands/feature/array-linear' />;
      } else {
        return <Icon name='commands/feature/array-circle' />;
      }
    }

    const IconComponent = treeIconMap[type];

    if (IconComponent) {
      return <AntIcon component={IconComponent} />;
    } else {
      return CADIcons(props);
    }
  };

  const renameDoc = (node: ProductTreeNode) => {
    setNodeToRename(node);
    setRenameDocModalOpen(true);
  };
  const resortDoc = (node: ProductTreeNode) => {
    setNodeToResort(node);
    setResortDocModalOpen(true);
  };
  const exchangeDoc = (node: ProductTreeNode) => {
    setNodeToExchange(node);
    setExchangeDocModalOpen(true);
  };

  useEffect(() => {
    const eventController = new AbortController();

    const objectSelected = (selectedObject: any) => {
      if (selectedObject.from === PickFrom.ObjectTree) {
        return;
      }
      const { modelDxid, lineId, vertexId, meshId } = selectedObject.assisObj;
      const selectedObjectId: string =
        modelDxid || lineId || vertexId || meshId;
      if (!selectedObjectId) {
        cadAppErrorLog(
          'Selected object has unhandled id in properties',
          selectedObject,
        );
        return;
      }

      const basicRefObjIndex = basicRefIDs.indexOf(selectedObjectId);

      if (basicRefObjIndex !== -1) {
        setSelectedNodeChain([docTree.children[basicRefObjIndex]]);
        return;
      }

      const frontID = selectedObjectId.split(':')[0];
      if (referenceManager.getObjectById(frontID) != null) {
        // 当前假设reference都在一级几何体下
        const target = treeData[0].children
          .filter((n) => n.type === Types.GeomBox)
          .flatMap((n) => n.children)
          .find((n) => n.key === frontID);

        if (target) {
          setSelectedNodeChain([target]);
        }

        return;
      }

      let current = getProductPartSubModel(selectedObjectId);
      if (!current) {
        cadAppErrorLog(
          'Unsatisfied part or sub model data for selected object',
          selectedObject,
        );
        return;
      }

      const nodeChain = [current];
      while (current.parent) {
        nodeChain.unshift(current.parent);
        current = current.parent;
      }
      setSelectedNodeChain(nodeChain);
    };

    userEvents
      .on(
        COMMON_EVENTS.CLICK_NULL,
        () => setSelectedKeys([]),
        eventController.signal,
      )
      .on(CMEVENTS.MESH_SELECTED, objectSelected, eventController.signal)
      .on(CMEVENTS.EDGE_SELECTED, objectSelected, eventController.signal)
      .on(CMEVENTS.POINT_SELECTED, objectSelected, eventController.signal);

    userEvents.on(
      PRODUCT_EVENT.PENDING_RENAME_DOC,
      renameDoc,
      eventController.signal,
    );
    userEvents.on(
      PRODUCT_EVENT.PENDING_RESORT_DOC,
      resortDoc,
      eventController.signal,
    );
    userEvents.on(
      PRODUCT_EVENT.ASSEMBLY_PRODUCT_EXCHANG,
      exchangeDoc,
      eventController.signal,
    );
    userEvents.on(
      PRODUCT_EVENT.ASSEMBLY_TREE_CHANGED,
      refreshTree,
      eventController.signal,
    );

    userEvents.on(
      PickInputEvents.TargetPicked,
      setPickInputPickedTarget,
      eventController.signal,
    );

    userEvents.on(
      PRODUCT_EVENT.REVERSE_SELECTION,
      setReverseSelection,
      eventController.signal,
    );

    return () => eventController.abort();
  }, []);

  useEffect(() => {
    if (selectedNodeChain.length > 0) {
      const lastSelectableNode = selectedNodeChain.find(
        ({ key }) => !expandedKeys.includes(key),
      );

      if (lastSelectableNode) {
        setSelectedKeys([lastSelectableNode.key]);
      }
    }
  }, [selectedNodeChain, expandedKeys]);

  const transFormingAndTargetSelected =
    ['CmdCopyPart', 'CmdTransformPart'].includes(currentCmd?.id) &&
    !!pickInputPickedTarget;

  useEffect(() => {
    if (transFormingAndTargetSelected) {
      setSelectedKeys([pickInputPickedTarget]);
    }
  }, [pickInputPickedTarget]);

  const refreshTree = () => setSelectedKeys((prev) => [...prev]); // force re-render

  const setReverseSelection = (nodes: ProductTreeNode[]) => {
    setSelectedKeys(nodes.map((o) => o.key));
    const pickerAssembly = PickerMgr.getPicker<PickerAssembly>('assembly');
    nodes.forEach((node) => {
      userEvents.emit(PRODUCT_EVENT.ASSEMBLY_TREE_NODE_SELECTED, node, true);
      if (
        node.key !== docTree.key &&
        !node.type.startsWith('assembly-constraint') &&
        csEvent().state === STATE.DEFAULT
      ) {
        pickerAssembly.selectModels(
          getAllKeysOfRefDoc(node),
          undefined,
          PickFrom.ObjectTree,
        );
      }
    });
  };

  const handleNodeSelect = (keys: any[], { node, nativeEvent }) => {
    if (transFormingAndTargetSelected) {
      return;
    }

    userEvents.emit(PRODUCT_EVENT.ASSEMBLY_TREE_NODE_SELECTED, node, true);
    const pickerAssembly = PickerMgr.getPicker<PickerAssembly>('assembly');
    pickerAssembly
      .clearSelectedModel()
      .clearSelectedMesh()
      .clearSelectedLine()
      .clearSelectedVertex();

    ctx2.viewer.requestRender();

    // 基准面
    if (basicRefIDs.includes(node.key)) {
      setSelectedKeys([node.key]);

      // we don't update the isRecursiveVisible property in the data manager
      // and test the visibility of the object itself only to simplify the logic
      if (node.visible && csEvent().state === STATE.DEFAULT) {
        pickerAssembly.selectModels([node.key], undefined, PickFrom.ObjectTree);
      }
      return;
    }

    // 约束暂时不需要多选
    if (node.type.startsWith('assembly-constraint')) {
      setSelectedKeys([node.key]);
      return;
    }

    setSelectedNodeChain([]);

    if (
      node.key !== docTree.key &&
      !node.type.startsWith('assembly-constraint') &&
      csEvent().state === STATE.DEFAULT
    ) {
      pickerAssembly.selectModels(
        getAllKeysOfRefDoc(node),
        undefined,
        PickFrom.ObjectTree,
      );
    }

    const isMultiPick = nativeEvent?.ctrlKey || nativeEvent?.shiftKey;

    const newKeys = isMultiPick ? keys : [node.key];

    setSelectedKeys(newKeys);
    if (node.visible && documentInfo.type === 'bracket') {
      ctx2.viewer.sceneSetup.fitTargetObject([ctx2.cadScene.assisGroup]);
    }
  };

  return (
    <ConfigProvider
      theme={{
        ...AntDesignTheme,
        token: {
          ...AntDesignTheme.token,
          colorPrimaryBg: '#4e4ede',
        },
      }}>
      <StyledTree
        mockDisabled={transFormingAndTargetSelected}
        treeData={treeData}
        titleRender={(node) =>
          NodeTitle(
            node,
            isRoot(node),
            setNewDocModalOpen,
            setInsertDocModalOpen,
            invokeCmd,
            docTree,
          )
        }
        onRightClick={({ event, node }) => {
          const isNodeSelected = selectedKeys.includes(node.key);
          const isMultiPick = event.ctrlKey || event.shiftKey;
          const newKeys: string[] = isNodeSelected
            ? selectedKeys
            : isMultiPick
              ? [...selectedKeys, node.key]
              : [node.key];

          if (!isNodeSelected) {
            setSelectedKeys(newKeys);
          }

          if (
            (FeatureManager.isFeatureEnabled('assemble-root-resort') ||
              !isRoot(node)) &&
            !['assembly-constraints-root', 'measurement-root'].includes(
              node.type,
            ) &&
            newKeys.length === 1
          ) {
            designTreeMenuManager.show(event, ProductNodeMenu.id, node);
          }

          if (newKeys.length > 1) {
            designTreeMenuManager.show(event, ProductNodeMenu.id, newKeys);
          }
        }}
        showLine={{ showLeafIcon: false }}
        showIcon={true}
        icon={treeIcon}
        onExpand={(keys) => setExpandedKeys(() => keys.map(String))}
        onSelect={handleNodeSelect}
        expandedKeys={expandedKeys}
        selectedKeys={selectedKeys}
        defaultExpandAll={false}
        blockNode
        multiple
      />
      <NewDocModal
        open={newDocModalOpen}
        onClose={() => setNewDocModalOpen(false)}
      />
      <InsertDocModal
        open={insertDocModalOpen}
        onClose={() => setInsertDocModalOpen(false)}
      />
      <RenameDocModal
        open={renameDocModalOpen}
        onClose={() => setRenameDocModalOpen(false)}
        treeNode={nodeToRename}
      />
      <ResortDocModal
        open={resortDocModalOpen}
        onClose={() => setResortDocModalOpen(false)}
        treeNode={nodeToResort}
      />
      <ExchangeDocModal
        open={exchangeDocModalOpen}
        onClose={() => setExchangeDocModalOpen(false)}
        treeNode={nodeToExchange}
      />
    </ConfigProvider>
  );
};
